package com.consumer.config;

import com.consumer.utils.JacksonUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import feign.*;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;
import feign.form.spring.SpringFormEncoder;
import feign.optionals.OptionalDecoder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;

/**
 * @author Liunh
 */
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@Slf4j
public class FeignConfig implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        //requestTemplate.target(properties.getFileParticularDomain());
        final ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            final HttpServletRequest request = attributes.getRequest();
            final Enumeration<String> headerNames = request.getHeaderNames();
            if (headerNames != null) {
                while (headerNames.hasMoreElements()) {
                    String name = headerNames.nextElement();
                    String values = request.getHeader(name);
                    if ("content-length".equals(name)) {
                        continue;
                    }
                    requestTemplate.header(name, values);
                }
            }
        }
    }

    /**
     * 设置 Eureka Server的访问的用户名和密码
     */
    /*@Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("admin","admin123");
    }*/

    /*@Bean
    public okhttp3.OkHttpClient okHttpClient() {
        return new okhttp3.OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(2, TimeUnit.HOURS)
                .writeTimeout(2, TimeUnit.HOURS)
                .connectionPool(new ConnectionPool())
                .build();
    }*/

    @Bean
    @Primary
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
        return Feign.builder();
        //return Feign.builder().decoder(new JacksonDecoder());
    }

    /*@Bean
    public Contract feignContract() {
        return new Contract.Default();
    }*/

    /*@Bean
    public Logger.Level feignLoggerLevel() {
        //return Logger.Level.FULL;
        return Logger.Level.BASIC;
    }*/

    @Bean
    public Request.Options feignRequestOptions() throws InstantiationException, IllegalAccessException {
        return new Request.Options(10000, 2000 * 3600);
        //return new Request.Options(TimeUnit.SECONDS.toMillis(10000), TimeUnit.MILLISECONDS.getDeclaringClass().newInstance(), TimeUnit.SECONDS.toMillis(2000 * 3600), TimeUnit.MILLISECONDS.getDeclaringClass().newInstance(), true);
    }

    @Bean
    public Retryer feignRetry() {
        return new Retryer.Default();
    }

    @Bean
    @Primary
    @Scope("prototype")
    public Decoder decode() {
        final HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(this.customObjectMapper());
        final ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(jacksonConverter);
        //return new ResponseEntityDecoder(new SpringDecoder(objectFactory));
        return new FormDecoder(new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(objectFactory))));
        //return new FeignClientDecoder();
    }

    public class FormDecoder implements Decoder {

        private final ObjectMapper objectMapper = customObjectMapper();
        private final HttpMessageConverter<Object> messageConverter = new MappingJackson2HttpMessageConverter(objectMapper);

        private final Decoder delegate;

        public FormDecoder(Decoder delegate) {
            this.delegate = delegate;
        }

        @Override
        public Object decode(Response response, Type type) throws IOException {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = response.body().asInputStream().read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, bytesRead);
            }
            byte[] body = byteArrayOutputStream.toByteArray();
            String bodyStr = new String(body, StandardCharsets.UTF_8);
            //log.info(">>>>>> Feign response: {}", bodyStr);
            /*if (StringUtils.isNotBlank(bodyStr) && (bodyStr.contains("\\r") || bodyStr.contains("\\n") || bodyStr.contains("\\t"))) {
                bodyStr.replace("\\r","").replace("\\n","").replace("\\t","");
            }*/
            return delegate.decode(response.toBuilder().body(bodyStr, StandardCharsets.UTF_8).build(), type);
        }

        /*@Override
        public Object decode(Response response, Type type) throws IOException {
            if (response.status() == HttpStatus.NO_CONTENT.value() || response.body() == null) {
                return null;
            }
            Collection<String> contentTypeHeader = response.headers().getOrDefault("Content-Type", Collections.singletonList(MediaType.APPLICATION_OCTET_STREAM_VALUE));
            log.info("Response headers:{}", response.headers());
            String contentType;
            if (contentTypeHeader != null && !contentTypeHeader.isEmpty()) {
                contentType = contentTypeHeader.iterator().next();
            } else {
                contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
            }
            MediaType mediaType = MediaType.parseMediaType(contentType);
            String body = StreamUtils.copyToString(response.body().asInputStream(), StandardCharsets.UTF_8);
            return messageConverter.read((Class<?>) type, new FakeHttpInputMessage(body, mediaType));
        }*/
    }

    private static class SpringDecoderFactory implements ObjectFactory<HttpMessageConverters> {
        @Override
        public HttpMessageConverters getObject() {
            // 使用Spring的HttpMessageConverters来处理响应体转换
            return new HttpMessageConverters(new FormHttpMessageConverter());
        }
    }

    public static class FakeHttpInputMessage implements org.springframework.http.HttpInputMessage {
        private final String body;
        private final MediaType mediaType;

        public FakeHttpInputMessage(String body, MediaType mediaType) {
            this.body = body;
            this.mediaType = mediaType;
        }

        @Override
        public InputStream getBody() throws IOException {
            return new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
        }

        @Override
        public org.springframework.http.HttpHeaders getHeaders() {
            org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
            headers.setContentType(mediaType);
            return headers;
        }
    }

    @Bean
    @Primary
    @Scope("prototype")
    public Encoder encoder() {
        final HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(this.customObjectMapper());
        final ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(jacksonConverter);
        return new SpringFormEncoder(new SpringEncoder(objectFactory));
    }

    /*@Bean
    @Primary
    @Scope("prototype")
    public Encoder encoder() {
        final HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(this.customObjectMapper());
        final ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(jacksonConverter);
        return new SpringEncoder(objectFactory);
    }*/


    private ObjectMapper customObjectMapper() {
        //SimpleDateFormat smt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        final ObjectMapper objectMapper = JacksonUtil.mapper;
        //objectMapper.setDateFormat(smt);
        objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
        objectMapper.configure(JsonGenerator.Feature.IGNORE_UNKNOWN, true);
        objectMapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
        objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES, false);
        objectMapper.registerModule(new StringSanitizerModule());
        return objectMapper;
    }

    @Bean
    public ErrorDecoder feignError() {
        return (key, response) -> {
            if (response.status() == HttpStatus.BAD_REQUEST.value()) {
                log.error(">>>>>> 请求服务400参数错误,返回:{}", response.body());
            }
            if (response.status() == HttpStatus.CONFLICT.value()) {
                log.error(">>>>>> 请求服务409异常,返回:{}", response.body());
            }
            if (response.status() == HttpStatus.NOT_FOUND.value()) {
                log.error(">>>>>> 请求服务404异常,返回:{}", response.body());
            }
            return new ErrorDecoder.Default().decode(key, response);
        };
    }

    static class StringSanitizerModule extends SimpleModule {
        public StringSanitizerModule() {
            addDeserializer(String.class, new StdScalarDeserializer<String>(Class.class) {
                @Override
                public String deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
                    return StringUtils.trimToNull(jsonParser.getValueAsString());
                }
            });
        }
    }

}
